﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;

namespace Microsoft.Mtool.FSM
{
    [Serializable]
    public class FSM
    {
        public FSM()
        { }

        public static FSM LoadFromXML(string filePath)
        {
            using (StreamReader rder = new StreamReader(filePath))
            {
                XmlSerializer srlzer = new XmlSerializer(typeof(FSM));
                return srlzer.Deserialize(rder) as FSM;
            }
        }

        public void SaveToXML(TextWriter writer)
        {
            XmlSerializer srlzer = new XmlSerializer(typeof(FSM));            
            srlzer.Serialize(writer, this);                
        }

        public void SaveToXML(string filePath)
        {
            using (StreamWriter wter = new StreamWriter(filePath))
            {
                XmlSerializer srlzer = new XmlSerializer(typeof(FSM));
                srlzer.Serialize(wter, this);                
            }
        }

        private string version = "0.1";
        private List<State> states = new List<State>();
        private List<Action> actions = new List<Action>();       
        private Dictionary<string, State> stateMap = new Dictionary<string, State>();
        private Dictionary<string, Action> actionMap = new Dictionary<string, Action>();

        [XmlAttribute]
        public string Version
        {
            get { return version; }
            set { version = value; }
        }

        [XmlArray]
        public List<State> States
        {
            get { return states; }
            set { states = value; }
        }

        [XmlArray]
        public List<Action> Actions
        {
            get { return actions; }
            set { actions = value; }
        }

        public void Initialize()
        {
            // check duplicate action id
            foreach (Action act in actions)
            {
                if (actionMap.ContainsKey(act.Id))
                    throw new Exception("Duplicate ID action found!");
                actionMap.Add(act.Id, act);  
            }
            // check duplicate state id
            foreach (State st in states)
            {
                if(stateMap.ContainsKey(st.Id))
                    throw new Exception("Duplicate ID state found!");
                stateMap.Add(st.Id, st);               
            }
            foreach (Action act in actions)
            {
                if (act.ToState == null)
                    act.ToState = stateMap[act.To];
                else
                    act.To = act.ToState.Id;

                if (act.FromState == null)
                    act.FromState = stateMap[act.From];
                else
                    act.From = act.FromState.Id;

                // indirect attributes has been set, so please don't call GenDefault here
                //act.GenDefaults();

                switch (act.Type)
                {
                    case ActionType.Control:
                        act.FromState.NextActions.Add(act);
                        break;
                    default:
                        throw new Exception(string.Format("action type is not supported:{0}", act.Type.ToString()));
                }
            }
        }

        public List<Tour> GenerateTours(State entry, int nMinStep, int nMaxStep, int maxRepeat)
        {
            List<Tour> qualifiedTours = new List<Tour>();
            List<Tour> tours = new List<Tour>();

            for (int nStep = 0; nStep <= nMaxStep; ++nStep)
            {
                if (nStep == 0) tours.Add(new Tour(entry));
                else
                {
                    // clear the previous ones
                    List<Tour> oldTours = tours;
                    tours = new List<Tour>();

                    // check each end state
                    foreach(Tour t in oldTours)
                    {                
                        // check each next action        
                        foreach (Action ac in t.Exit.NextActions)
                        {
                            // calculate the repeat count
                            int nCount = 0;
                            foreach (Action a in t.Actions)
                                if (a.Id == ac.Id) nCount++;
                            if (nCount >= maxRepeat) continue;
                            // extend the length by one
                            Tour newTour = t.Clone() as Tour;
                            newTour.Apply(ac);                           
                            tours.Add(newTour);
                            // save the found tour if possible
                            if (nStep < nMinStep) continue;
                            if (newTour.Exit.Type != StateType.Exit) continue;
                            if (newTour.Exit.NextActions.Count != 0 && ac.From != newTour.Exit.Id) continue;
                            qualifiedTours.Add(newTour);
                        }
                    }
                }
            }
            return qualifiedTours;
        }

        public Action GetAction(string id)
        {
            if(actionMap.ContainsKey(id))
                return actionMap[id];
            return null;
        }

        public State GetState(string id)
        {
            if (stateMap.ContainsKey(id))
                return stateMap[id];
            return null;
        }
    }

    public class DataTest
    {
        public static void TestSaveSth()
        {
            FSM mtool = new FSM();
            mtool.Version = "0.1";
            mtool.Actions = new List<Action>();
            mtool.States = new List<State>();
            
            //states
            State s0 = new State("s0", StateType.Entry);
            State s1 = new State("s1", StateType.Free);
            State s2 = new State("s2", StateType.Free);
            
            //stubs            
            Parameter p_in = new Parameter();
            p_in.Name = "param_in";
            p_in.Var.Name = "t_0_p_in";
            p_in.Var.MergedValues = "t_0_p_in_0, t_0_p_in_1, t_0_p_in_2";

            Parameter p_out = new Parameter();
            p_out.Name = "param_out";
            p_out.Var.Name = "t_0_p_out";
            p_out.Var.MergedValues = "t_0_p_out_0, t_0_p_out_1, t_0_p_out_2";

            TokenImpl t_0 = new TokenImpl();
            t_0.Name = "token_0";
            t_0.InParas = new List<Parameter>();
            t_0.InParas.Add(p_in);
            t_0.OutParas = new List<Parameter>();
            t_0.OutParas.Add(p_out);

            //actions
            Action a0_1 = new Action("a0_1", ActionType.Control, ActionSource.Token, s0, s1);
            Action a1_2 = new Action("a1_2", ActionType.Control, ActionSource.Token, s1, s2);
            Action a2_1 = new Action("a2_1", ActionType.Control, ActionSource.Token, s2, s1);
            Action a2_0 = new Action("a2_0", ActionType.Control, ActionSource.Token, s2, s0);
            Action a1_1 = new Action("a1_1", ActionType.Control, ActionSource.Token, s1, s1);
            Action a2_2 = new Action("a2_2", ActionType.Control, ActionSource.Token, s2, s2);
            a0_1.Stubs.Add(t_0);
            a1_2.Stubs.Add(t_0);
            a2_1.Stubs.Add(t_0);
            a2_0.Stubs.Add(t_0);
            a1_1.Stubs.Add(t_0);
            a2_2.Stubs.Add(t_0);

            mtool.Actions.AddRange(new Action[]{a0_1,a1_1,a1_2,a2_1,a2_0,a2_2});
            mtool.States.AddRange(new State[] { s0,s1,s2 });

            XmlSerializer srlzer = new XmlSerializer(typeof(FSM));
            srlzer.Serialize(new StreamWriter("mtool.sample.output.xml"), mtool);
            /*Variable v = new Variable();
            v.Name = "var";
            XmlSerializer srlzer = new XmlSerializer(typeof(Variable));
            srlzer.Serialize(new StreamWriter("mtool.sample.output.xml"), v);*/
        }

        public static void TestLoadSth()
        {                 
            XmlSerializer srlzer = new XmlSerializer(typeof(FSM));
            FSM tool = srlzer.Deserialize(new StreamReader("mtool.sample.input.xml")) as FSM;
            System.Diagnostics.Debug.Assert(tool != null);
        }
    }
}
